package logic

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	wxutils "github.com/wechatpay-apiv3/wechatpay-go/utils"

	"laundry-service/app/client/internal/config"
	"laundry-service/app/client/internal/svc"
	"laundry-service/app/client/internal/types"
	"laundry-service/entity"

	"github.com/zeromicro/go-zero/core/logx"
)

type WxPaymentCallbackLogic struct {
	logx.Logger
	req    *http.Request
	svcCtx *svc.ServiceContext
	wxPkg  map[string]string
}

type WuYouXMLResponse struct {
	Head       string `xml:"head" json:"head"`
	SystemCode string `xml:"system_code" json:"system_code"`
	OrderCode  string `xml:"order_code" json:"order_code"`
	WaybillNos string `xml:"waybill_nos" json:"waybill_nos"`
}

func NewWxPaymentCallbackLogic(req *http.Request, svcCtx *svc.ServiceContext) *WxPaymentCallbackLogic {
	return &WxPaymentCallbackLogic{
		Logger: logx.WithContext(req.Context()),
		req:    req,
		svcCtx: svcCtx,
		wxPkg:  nil,
	}
}

func DecodeNotify(body []byte, apiV3key string) (bool, map[string]string) {
	var prepaymap map[string]interface{}
	_ = json.Unmarshal(body, &prepaymap)

	var prepaymap2 = prepaymap["resource"].(map[string]interface{})
	nonce := prepaymap2["nonce"].(string)
	associatedData := prepaymap2["associated_data"].(string)
	ciphertext := prepaymap2["ciphertext"].(string)

	var datamap map[string]string
	tx, err := wxutils.DecryptAES256GCM(apiV3key, associatedData, nonce, ciphertext)
	if err != nil {
		log.Println(err)
		return false, datamap
	}
	_ = json.Unmarshal([]byte(tx), &datamap)
	return true, datamap
}

func (l *WxPaymentCallbackLogic) mockPaymentLocalBusiness(num string, succFlag string) {
	fmt.Println("mockPaymentLocalBusiness", num, succFlag)
}

func (l *WxPaymentCallbackLogic) refundByProcessOrderFailed(order *entity.Order, payment *entity.OrderPayment, orderLog *entity.OrderLog) error {
	_ = l.svcCtx.Db.TxSaveOrderState(order, payment, orderLog)
	t, _ := CreateRefundTokenByOrder(l.svcCtx.Db, order)
	responseBin, err := WxRefoundRequest(l.req.Context(), *l.svcCtx, order.Number, t.RefundNo, order.RealAmount)

	var logger = entity.NewStateLogWithOrder(order, 3, "申请退款", "")
	if err != nil {
		logger.Attr = fmt.Sprintf("退款请求失败：%v", err)
	} else {
		logger.Attr = fmt.Sprintf("退款请求成功: %s", string(responseBin))
	}
	return l.svcCtx.Db.SavePaymentLog(logger)
}

func (l *WxPaymentCallbackLogic) LocalBusiness() error {
	orderNo := l.wxPkg["out_trade_no"]
	order, err := l.svcCtx.Db.GetOrderByNumber(orderNo)
	orderLog := entity.NewFailedOrderLogWithOrder("", order, "")
	if err != nil {
		order.State = -2
		orderLog.Remark = "交易失败"
		orderLog.Attr = fmt.Sprintf("未找到订单: %s, %v", orderNo, err)
		return l.svcCtx.Db.TxSaveOrderState(order, nil, orderLog)
	}

	if order.PayState != 0 {
		logx.Infof("订单已支付或已申请退款: %s", orderNo)
		return nil
	}

	order.PayState = 1
	order.PayAt = time.Now()
	wxJson, _ := json.Marshal(l.wxPkg)
	payment := entity.NewOrderPayment(order, l.wxPkg["transaction_id"], l.wxPkg["trade_state"])

	if order.State != 0 {
		orderLog.Remark = fmt.Sprintf("微信重复通知 %d", order.ID)
		orderLog.Attr = string(wxJson)
		return l.svcCtx.Db.TxSaveOrderState(nil, nil, orderLog)
	}

	if payment.TradeState != "SUCCESS" {
		order.State = -2
		orderLog.Remark = "微信支付失败"
		orderLog.Attr = string(wxJson)
		return l.svcCtx.Db.TxSaveOrderState(order, nil, orderLog)
	}
	payment.Attr = string(wxJson)
	senderAddr, deliveryAddr, err := checkOrderAddrInfo(l.svcCtx, order)
	if err != nil {
		order.State = -2
		orderLog.Remark = "交易失败"
		orderLog.Attr = fmt.Sprintf("地址信息异常: %v", err)
		return l.refundByProcessOrderFailed(order, payment, orderLog)
	}
	prodsOfOrder, err := GetOrderProductInfo(l.svcCtx, order.ID)
	if err != nil {
		order.State = -2
		orderLog.Remark = "交易失败"
		orderLog.Attr = fmt.Sprintf("产品信息异常: %v", err)
		return l.refundByProcessOrderFailed(order, payment, orderLog)
	}

	reservationOrder, err := l.svcCtx.Db.FindReservationOrderByOrderId(order.ID)
	if err != nil {
		logx.Error("未找到预约订单: ", order.ID, err)
		return nil
	}
	paramXmlDoc := PlacePickOrderDoc(reservationOrder.AppointmentTime, l.svcCtx.Config, senderAddr, deliveryAddr, prodsOfOrder, order.Number)
	WuYouResp, err := l.PostReq("/shopping/placePickOrder", order, paramXmlDoc)
	if err != nil {
		order.State = -2
		orderLog.Remark = "交易失败"
		orderLog.Attr = fmt.Sprintf("%v", err)
		return l.refundByProcessOrderFailed(order, payment, orderLog)
	}
	order.State = 1
	order.ExpressBillCode = WuYouResp.WaybillNos
	order.ExpressOrderCode = WuYouResp.SystemCode
	orderLog.Remark = "交易成功"
	orderLog.State = 1
	return l.svcCtx.Db.TxSaveOrderState(order, payment, orderLog)
}

func checkOrderAddrInfo(l *svc.ServiceContext, order *entity.Order) (*entity.Address, *entity.Address, error) {
	reservationOrder, err := l.Db.FindReservationOrderByOrderId(order.ID)
	if err != nil {
		return nil, nil, err
	}
	senderAddr, err := l.Db.FindAddrById(int64(order.UserId), reservationOrder.PickupAddrId)
	if err != nil {
		return nil, nil, err
	}
	deliveryAddr, err := l.Db.FindAddrById(int64(order.UserId), reservationOrder.DeliveryAddrId)
	if err != nil {
		return nil, nil, err
	}
	fmt.Println("预约订单地址查询成功: ", senderAddr, deliveryAddr)
	return senderAddr, deliveryAddr, nil
}

func GetOrderProductInfo(l *svc.ServiceContext, orderId int) ([]entity.SFProduct, error) {
	productItems, err := l.Db.GetProductItem(orderId)
	if err != nil {
		return nil, err
	}

	itemCount := len(productItems)
	if itemCount == 0 {
		return nil, fmt.Errorf("not found product in order: %d", orderId)
	}
	prods := make([]entity.SFProduct, itemCount)

	for i, v := range productItems {

		orderItem, err := l.Db.GetOrderItemByOrderIdAndProductId(orderId, v.ID)
		if err != nil {
			logx.Errorf("未找到订单商品: %v, orderId: %v, 商品Id: %v;", err, orderId, v.ID)
			return nil, err
		}

		prods[i].ProdId = strconv.Itoa(int(v.ID))
		prods[i].Name = v.Name
		prods[i].Price = v.Price
		prods[i].Num = orderItem.ProductNum
	}
	return prods, nil
}

func PlacePickOrderDoc(appointmentTime string, config config.Config, sender *entity.Address, delivery *entity.Address, prodInfo []entity.SFProduct, orderNum string) []byte {
	senderParts := strings.Split(sender.Region, ",")
	deliveryParts := strings.Split(delivery.Region, ",")

	req := entity.ReqPickOrder{
		Head: config.WY.Header,
		Order: entity.PickOrder{
			OrderCode:     orderNum,
			SendStartTime: appointmentTime,
			Sender: entity.OrderAddr{
				Name:     sender.Name,
				Province: senderParts[0],
				City:     senderParts[1],
				County:   senderParts[2],
				Tel:      sender.Phone,
				Detail:   sender.Detail,
			},
			Delivery: entity.OrderAddr{
				Name:     delivery.Name,
				Province: deliveryParts[0],
				City:     deliveryParts[1],
				County:   deliveryParts[2],
				Tel:      delivery.Phone,
				Detail:   delivery.Detail,
			},
			Products: prodInfo,
			ExpCom:   "sf",
		},
	}

	xmlData, err := xml.MarshalIndent(req, "", "  ")
	if err != nil {
		panic(err)
	}
	return xmlData
}

func (l *WxPaymentCallbackLogic) PostReq(apiPart string, order *entity.Order, paramXmlDoc []byte) (*WuYouXMLResponse, error) {
	_ = l.svcCtx.Db.SavePaymentLog(entity.NewStateLogWithOrder(order, 1, "请求五优订单", string(paramXmlDoc)))

	url := fmt.Sprintf("%s%s", l.svcCtx.Config.WY.BaseURL, apiPart)

	reqBody := bytes.NewBuffer([]byte("aesEncode=0&requestXml="))
	reqBody.Write(paramXmlDoc)
	resp, err := http.Post(url, "application/x-www-form-urlencoded", reqBody)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, err
	}

	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			logx.Errorf("close stream failed when request WU_YOU api: err=%v", err)
			return
		}
	}(resp.Body)
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	_ = l.svcCtx.Db.SavePaymentLog(entity.NewStateLogWithOrder(order, 1, "五优下单返回", string(respBody)))

	var wuyouResponse WuYouXMLResponse
	err = xml.Unmarshal(respBody, &wuyouResponse)
	if err != nil {
		return nil, fmt.Errorf("parser xml response err=%v", err)
	} else if strings.ToUpper(wuyouResponse.Head) != "OK" {
		return nil, fmt.Errorf("五优下单失败")
	}
	return &wuyouResponse, nil
}

func (l *WxPaymentCallbackLogic) safetyLocalBusinessCaller() {
	defer func() {
		if r := recover(); r != nil {
			stack := string(debug.Stack())
			logx.Errorf("Order payment crash with: %v%s", r, stack)
		}
	}()
	_ = l.LocalBusiness()
}

func (l *WxPaymentCallbackLogic) WxPaymentCallback() (resp *types.Reply, err error) {
	body, _ := io.ReadAll(l.req.Body)
	b, s := DecodeNotify(body, l.svcCtx.Config.WxPay.Apiv3Key)
	if !b {
		return nil, errors.New("验签错误")
	}
	l.wxPkg = s

	logx.Infof("Wechat-pay notification: %v", l.wxPkg)

	orderNumber := s["out_trade_no"]
	if orderNumber == "zmel_test_v1" {
		l.mockPaymentLocalBusiness(orderNumber, s["trade_state"])
		return
	} else {
		l.safetyLocalBusinessCaller()
	}
	return &types.Reply{}, nil
}
